/*************************************************************
 *
 *  web-math-edit.js
 *  
 *  The main code for the web-math-edit utilities , including the
 *  event listeners and subroutines to manipulate MathML on the page
 *  
 *  ---------------------------------------------------------------------
 *  
 *  Copyright (c) 2012-Infinity Evgeny Savelev of Blacksburg, VA.
 * 
 *  Licensed under the GPL, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *      http://www.gnu.org/licenses/
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */



var EXTRACTNODE=true;

var mathRoot;

var mathMLnamespace="http://www.w3.org/1998/Math/MathML";
var mathBlinkingOn=false,mathBlinkingTimer,
	mathBlinkingObj=
	{
		obj:null,
		side:null,
		selectedMrow:null
	};

var highlightedNodes=[];
var mathHighlightingInProgress=false,mathHighlightingTimer,
	mathHighlightingObj=
	{
		start:null,//Where did the selection start
		startside:"",//On what side of the object it did 
		end:null,//Current object receiving mouseover events
		endside:"",//on which side mouse hovers
	};

var mathDraggingOn=false, mathDraggingTimer,
	mathDraggingObj=
	{
		startScreenX:0,
		startScreenY:0
	};

var greyedOutElements = //insert a greyed out element
	{
		inputRequired: "?", //if an operation is not followed by an operand
		times		:	String.fromCharCode(0xD7),	//Cross between two numbers
	//	times		:	String.fromCharCode(0xB7),	//Middle dot
		leadingDot	:	"0",	//put a greyed out zero in front of a dot
		trailingDot	:	"0"	,	//put a greyed out zero after a dot
		argumentRequired : "?" //if a <functionname mrow> is not followed by <functionargument mrow>
	}
	
var joinableTags=["mn"];//tags to join in homogeneous rows
var functionsRecognized = ["sinh","sin","asin","arcsin"];//sequences of <mi> elements to be converted into <functionname mrow>

function setUpSelectables()
//This function scans the page for editable MathML sections and call a processor on them. TODO change the search from by ID to by class-name
{
	var editableMathList=document.getElementsByClassName('editablemath');
	
	for (var i=0;i<editableMathList.length;i++)
	{
		editableMathList[i].addEventListener("mousedown",mathOnMouseDown,false);
		
		editableMathList[i].addEventListener("mouseup",mathOnMouseUp,false);
		
		editableMathList[i].addEventListener("mousemove",mathOnMouseMove,false);
		
		editableMathList[i].addEventListener("mouseout",mathOnMouseOut,false);
		
		editableMathList[i].addEventListener("click",mathOnMouseClick,false);
//		processDescendants(editableMathList[i],0);	
	}
}

function attachClassName(node,classname)
{
	var classstring=node.getAttribute('class');
	var patt = new RegExp(classname);
	
	if (null == classstring) classstring ='';
	
	if(patt.test(classstring))
	{
		return;
	}
	else
	{
		node.setAttribute('class',classstring + " " + classname);
	}
}

function clearClassName(node,classname)
{
	var classstring=node.getAttribute('class');
	var patt = new RegExp("\\s*"+classname);
	
	if (null == classstring) return;
	
	if(patt.test(classstring))
	{
		node.setAttribute('class',classstring.replace(patt,""));
	}
}

function mathOnMouseDown(evt)
{
	if ((evt.altKey)||(evt.ctrlKey)||(evt.metaKey))
	{
	}
	else
	{
		if (evt.button==0)
		{//if left mouse button is pressed
		
			mathStopBlinking();
			
			var obj = evt.target;
			var XPos = evt.clientX;
			var YPos = evt.clientY;
		   
			var objRect = obj.getBoundingClientRect();
			var objTop = Math.round(objRect.top),
				objLeft = Math.round(objRect.left),
				objWidth = obj.scrollWidth,
				objHeight = obj.scrollHeight;
				
			var side="left";
			
			if (((XPos-objLeft)/objWidth)>1/2) side="right";
			
			var stylestring = obj.getAttribute('style');
		
			if(stylestring!=null)
			{
				stylestring=stylestring.replace(/border-.*[^;];/,"");
				obj.setAttribute('style',stylestring);
			}
			
			mathRoot=obj;
			while (mathRoot.nodeName!="math") mathRoot=mathRoot.parentNode;
					
			if (highlightedNodes.length==0)
			{
				if (evt.shiftKey)
				{//check if shift key was pressed
					mathHighlightingObj.start=mathBlinkingObj.obj;
					mathHighlightingObj.startside=mathBlinkingObj.side;
					if(mathHighlightingObj.start!=null)
					{
						mathHighlightingObj.end=obj;
						mathHighlightingObj.endside=side;
						
						highlightNodes("hlinprogress");
						
						try {clearTimeout( mathHighlightingTimer );} catch(e){};
						try {clearTimeout( mathDraggingTimer );} catch(e){};
						
						mathHighlightingInProgress=true;
					}
				}
				else
				{
					mathHighlightingObj.start=obj;
					mathHighlightingObj.startside=side;
					
					try {clearTimeout( mathHighlightingTimer );} catch(e){};
					try {clearTimeout( mathDraggingTimer );} catch(e){};
					
					mathHighlightingTimer=setTimeout(function (){mathHighlightingInProgress=true;},200);
				}				
				if (evt. preventDefault) evt.preventDefault();
			}
			else
			{
				if (evt.shiftKey)
				{//check if shift key was pressed
					if(mathHighlightingObj.start!=null)
					{
						mathHighlightingObj.end=obj;
						mathHighlightingObj.endside=side;
						
						highlightNodes("hlinprogress");
						
						try {clearTimeout( mathHighlightingTimer );} catch(e){};
						try {clearTimeout( mathDraggingTimer );} catch(e){};
						
						if (highlightedNodes.length>0) mathHighlightingInProgress=true;
					}
					if (evt. preventDefault) evt.preventDefault();
				}
				else if(isHighlighted(obj))
				{// else, check if we have mousedown on the highlighted area, this begin the drag-n-drop
					mathDraggingObj.startScreenX=XPos;
					mathDraggingObj.startScreenY=YPos;

					try {clearTimeout( mathHighlightingTimer );} catch(e){};
					try {clearTimeout( mathDraggingTimer );} catch(e){};
					
					mathDraggingTimer=setTimeout(function (){mathDraggingOn=true;attachClassName(mathRoot,'draggignon');},200);

					if (evt. preventDefault) evt.preventDefault();
				}
				else
				{// else, check if we clicked on something not highlighted, possibly start new highlighting process
					mathHighlightingObj.start=obj;
					mathHighlightingObj.startside=side;
					
					try {clearTimeout( mathHighlightingTimer );} catch(e){};
					try {clearTimeout( mathDraggingTimer );} catch(e){};
					
					mathHighlightingTimer=setTimeout(function (){unHighlightNodes();mathHighlightingInProgress=true;},200);
					
					if (evt. preventDefault) evt.preventDefault();
				}
			}
		}
	}
}

function mathOnMouseUp(evt)
{
	try {clearTimeout( mathHighlightingTimer );} catch(e){};
	try {clearTimeout( mathDraggingTimer );} catch(e){};
	
	if (mathHighlightingInProgress) 
	{
		for (var i=0;i<highlightedNodes.length;i++)
		{
			clearClassName(highlightedNodes[i],'hlinprogress');
			attachClassName(highlightedNodes[i],'highlighted');
		}
		mathHighlightingInProgress=false;
		mathDraggingOn=false;
	}
	else if (mathDraggingOn) 
	{
		mathDraggingOn=false;
		clearClassName(mathRoot,'draggignon');
		clearClassName(mathRoot,'draggignnodrop');
		//Drop everything if we can.
	}
	else
	{
		unHighlightNodes();
	}
	
	mathResumeBlinking();
}

function mathOnMouseMove(evt)
{
	var obj = evt.target,stylestring=obj.getAttribute('style');
	var XPos = evt.clientX;
	var YPos = evt.clientY;
   
	var objRect = obj.getBoundingClientRect();
	var objTop = Math.round(objRect.top),
		objLeft = Math.round(objRect.left),
		objWidth = obj.scrollWidth,
		objHeight = obj.scrollHeight;
		
	var side="left",
		editableobj=isEditable(obj);
	
	if (((XPos-objLeft)/objWidth)>1/2) side="right";
	
	if(mathHighlightingInProgress)
	{//We are selecting stuff
		if(mathHighlightingObj.start==null)// it should not be
		{
			mathHighlightingObj.start=mathBlinkingObj.obj;
			mathHighlightingObj.startside=mathBlinkingObj.side;
		}
		
		mathHighlightingObj.end=obj;
		mathHighlightingObj.endside=side;
		
		highlightNodes("hlinprogress");
	}
	else if(mathDraggingOn)
	{
		if(editableobj)
		{
			if(isHighlighted(obj))
			{
				clearClassName(mathRoot,'draggignon');
				attachClassName(mathRoot,'draggignnodrop');
			}
			else
			{
				clearClassName(mathRoot,'draggignnodrop');
				attachClassName(mathRoot,'draggignon');
			}
		}
		else
		{
			clearClassName(mathRoot,'draggignon');
			attachClassName(mathRoot,'draggignnodrop');
		}
	}
	//Mouse is just moving
	if(editableobj)
	{
		if (stylestring!=null)
		{
			var parentBlink=/blink-*parent/.test(stylestring),
				blinkLeftOnly=/blink-*\w*-*left/.test(stylestring),
				blinkRightOnly=/blink-*\w*-*right/.test(stylestring);
				
			//**********************************************DO NOW insert the code of finding the right item to blink	
				
			obj.removeAttribute('style');
	
			if (side=="right")
			{
				obj.setAttribute('style',stylestring.replace(/border.*[^;];/,"")+"border-right: 1px solid black ;");
			}
			else
			{
				obj.setAttribute('style',stylestring.replace(/border.*[^;];/,"")+"border-left: 1px solid black ;");
			}
		}
		else
		{
			if (side=="right")
			{
				obj.setAttribute('style',"border-right: 1px solid black ;");
			}
			else
			{
				obj.setAttribute('style',"border-left: 1px solid black ;");
			}
		}
	}
	if (evt. preventDefault) evt.preventDefault();
}

function mathOnMouseOut(evt)
{
	var obj = evt.target,stylestring=obj.getAttribute('style');
	
	if((!mathHighlightingInProgress))
	{
		if (stylestring!=null)
		{
			obj.removeAttribute('style');
			obj.setAttribute('style',stylestring.replace(/background-color\s*:(.*)\s*;/,"").replace(/border.*[^;];/,""));
		}
	}
	else
	{
		if (stylestring!=null)
		{
			obj.setAttribute('style',stylestring.replace(/border.*[^;];/,""));
		}
	}
}

function mathOnMouseClick(evt)
{
	if(highlightedNodes.length==0)
	{
		var obj = evt.target;
							
		var XPos = evt.clientX;
		var YPos = evt.clientY;
	   
		var objRect = obj.getBoundingClientRect();
		var objTop = Math.round(objRect.top),
			objLeft = Math.round(objRect.left),
			objWidth = obj.scrollWidth,
			objHeight = obj.scrollHeight;
			
		var selectedNode;
	
		try {clearTimeout( mathHighlightingTimer );} catch(e){};
		try {clearTimeout( mathDraggingTimer );} catch(e){};
	
		if (((XPos-objLeft)/objWidth)>1/2)
		{
			if (obj.nodeName==="mrow")
			{
				selectedNode=obj;
				var child =obj.lastElementChild;
				mathBlinkSetUp(child,"right",selectedNode);
			}
			else
			{
				selectedNode=findParentMRow(obj);
				if (selectedNode!=null) mathBlinkSetUp(obj,"right",selectedNode);
			}
		}
		else
		{
			if (obj.nodeName==="mrow")
			{
				selectedNode=obj;
				var child =obj.firstElementChild;
				mathBlinkSetUp(child,"left",selectedNode);
			}
			else
			{
				selectedNode=findParentMRow(obj);
				if (selectedNode!=null) mathBlinkSetUp(obj,"left",selectedNode);
			}
		}
	}
}

function processDescendants(root,deep)
{
	var children=root.childNodes,item,classname;
	
	for (var i=0;i<children.length;i++)
	{
		item=children[i];
		
		if (item.getAttribute)
		{
			classname =item.getAttribute('class');
			if(classname!=null)
			{
				if((item.nodeName==="mrow")&&(classname.indexOf("selectable")>-1))
				{
					item.addEventListener("mousedown",mathOnMouseDown,false);
					
					item.addEventListener("mouseup",mathOnMouseUp,false);
					
					item.addEventListener("mousemove",mathOnMouseMove,false);
					
					item.addEventListener("mouseout",mathOnMouseOut,false);
					
					item.addEventListener("click",mathOnMouseClick,false);
				}
			}
		}
		
		if (item.nodeType===1) {processDescendants(item,deep+1);}
	}
}

function findLCA(node1,node2)
{
	var parent1list=[],parent2list=[];
	
	var parentnode=node1, mathRoot, l1,l2,i=1;
	
	while (parentnode.nodeName!="math")
	{// loop through all ancestors of node1
		parentnode=parentnode.parentNode;
		parent1list.push(parentnode);
	}
	mathRoot=parentnode;
	parentnode=node2;
	while (parentnode.nodeName!="math")
	{// loop through all ancestors of node2
		parentnode=parentnode.parentNode;
		parent2list.push(parentnode);
	}
	
	l1=parent1list.length;
	l2=parent2list.length;
	
	while ((parent1list[l1-i]==parent2list[l2-i])&&(i<=l1)&&(i<=l2))
	{
		mathRoot=parent1list[l1-i];
		i++;
	}
	
	return mathRoot;
}

function isAtomic(node)
{//Check if the math element is not supposed to be taken apart
	if((node)&&(node.namespaceURI.indexOf("MathML") > -1))
	{
		if (/(mn|mo|mi|mfrac|msub|msup|msubsup)/.test(node.nodeName))
		{
			return true
		}
	}
	
	return false;
}

function isHighlighted(node)
{//Check if the node of interest is a part of a highlighted structure (for drag-n-drop applications)
	if ((node)&&(node.namespaceURI.indexOf("MathML") > -1))
	{
		var nodeIterator=node;
	
		while (nodeIterator.nodeName!="math")
		{
			if (/highlighted/.test(nodeIterator.getAttribute('class'))) return true;
			
			nodeIterator=nodeIterator.parentNode;
		}
	}
	return false;
}

function isEditable(node)
{
	if ((node)&&(node.namespaceURI.indexOf("MathML") > -1))
	{
		var nodeIterator=node;
	
		while (nodeIterator.nodeName!="math")
		{
			if (/selectable/.test(nodeIterator.getAttribute('class'))) return true;
			
			nodeIterator=nodeIterator.parentNode;
		}
	}
	return false;
}

function isMovable(node)
{
	if ((node)&&(node.namespaceURI.indexOf("MathML") > -1))
	{
		if(/mathmovable/.test(node.getAttribute('class'))) return true;
	}
	return false;
}

function unHighlightNodes()
{
	for(var i=0;i<highlightedNodes.length;i++)
	{
		clearClassName(highlightedNodes[i],"highlighted");
		clearClassName(highlightedNodes[i],"hlinprogress");
	}
	highlightedNodes=[];
}

function highlightNodes(classname)
{
	var startNode=mathHighlightingObj.start,endNode=mathHighlightingObj.end;
	var parent1list=[startNode],parent2list=[endNode],templist=null;
	
	var parentnode=startNode, childIterator, l1,l2,i=1;
	
	if((startNode)&&(endNode))
	{
	
		if(startNode==endNode)
		{
			if (mathHighlightingObj.startside!=mathHighlightingObj.endside)
			{
				unHighlightNodes();
				highlightedNodes.push(startNode);
				attachClassName(startNode,classname);
				mathBlinkSetUp(startNode,"right",findParentMRow(startNode));
				mathStopBlinking();
			}
		}
		else
		{
			var leftNode=startNode,rightNode=endNode,direction="right";
			var leftNodeSide=mathHighlightingObj.startside,rightNodeSide=mathHighlightingObj.endside;
			
			while (parentnode.nodeName!="math")
			{// loop through all ancestors of startNode
				parentnode=parentnode.parentNode;
				parent1list.push(parentnode);
			}
			parentnode=endNode;
			while (parentnode.nodeName!="math")
			{// loop through all ancestors of endNode
				parentnode=parentnode.parentNode;
				parent2list.push(parentnode);
			}
			
			l1=parent1list.length;
			l2=parent2list.length;
			
			while ((parent1list[l1-i]==parent2list[l2-i])&&(i<l1)&&(i<l2))
			{
				parentnode=parent1list[l1-i];
				i++;
			}
			
			unHighlightNodes();
			
			if ((parentnode.nodeName!="mrow")||(!(/selectable/.test(parentnode.getAttribute('class')))))
			{
				if(findParentMRow(parentnode)==null)
				{
					return null;
				}
			}
			
			if (isAtomic(parentnode))
			{
				highlightedNodes.push(parentnode);
				attachClassName(parentnode,classname);
				mathBlinkSetUp(parentnode,"right",findParentMRow(parentnode));
				mathStopBlinking();
			}
			else
			{
				childIterator=parentnode.firstElementChild;
				
				leftNode=parent1list[l1-i];//the highest unique ancestor for startNode
				rightNode=parent2list[l2-i];//the highest unique ancestor for endNode
			
				while ((leftNode!=childIterator)&&(rightNode!=childIterator))
				{
					childIterator=childIterator.nextElementSibling;
				}
	
				if (rightNode==childIterator)
				{
					rightNode=leftNode;
					rightNodeSide=mathHighlightingObj.startside;
					templist=parent2list;			
					parent2list=parent1list;
					
					leftNode=childIterator;
					leftNodeSide=mathHighlightingObj.endside
					parent1list=templist;
					templist=null;
					
					l1=parent1list.length;
					l2=parent2list.length;
					direction="left";
				}
				
				
				//process highlighting of the start
				if (childIterator!=null)
				{
					if (/homogeneous/.test(childIterator.getAttribute('class')))
					{//we started in the middle of a row of numbers
						childIterator=parent1list[l1-i-1];
						
						if(leftNodeSide=="left")
						{//we started on the left of leftNode, include it
							highlightedNodes.push(childIterator);
							attachClassName(childIterator,classname);
						}// else we strted on the right of leftNode, do not include it
	
						while (childIterator.nextElementSibling!=null)
						{
							childIterator=childIterator.nextElementSibling;
							highlightedNodes.push(childIterator);
							attachClassName(childIterator,classname);
						}
					}
					else
					{
						if (l1-i>0)
						{//if the start point is too deep in the tree, we just select the whole parent
							highlightedNodes.push(childIterator);
							attachClassName(childIterator,classname);
						}
						else
						{//we are not too deep in the tree, we can afford to be picky
							if(leftNodeSide=="left")
							{//we started on the left of obj.start, include it
								highlightedNodes.push(childIterator);
								attachClassName(childIterator,classname);
							}// else we strted on the right of obj.start, do not include it
						}
					}
				}
				else return null;
								
				if (leftNode!=rightNode)
				{
					childIterator=leftNode.nextElementSibling;
					
					//process highlighting of the midnodes
					while ((rightNode!=childIterator))
					{
						if (childIterator!=null)
						{
							highlightedNodes.push(childIterator);
							attachClassName(childIterator,classname);
						}
						else break;
						childIterator=childIterator.nextElementSibling;
					}
					
					//process highlighting of the end
					if (childIterator!=null)
					{
						if (/homogeneous/.test(childIterator.getAttribute('class')))
						{//we ended in the middle of a row of numbers
							childIterator=parent2list[l2-i-1];
							
							if(rightNodeSide=="right")
							{//we ended on the right of rightNode, include it
								highlightedNodes.push(childIterator);
								attachClassName(childIterator,classname);
								mathBlinkSetUp(childIterator,"right",findParentMRow(childIterator));
								mathStopBlinking();
							}// else we ended on the left of rightNode, do not include it
							else
							{
								mathBlinkSetUp(childIterator,"left",findParentMRow(childIterator));
								mathStopBlinking();
							}
							
							while (childIterator.previousElementSibling!=null)
							{
								childIterator=childIterator.previousElementSibling;
								highlightedNodes.push(childIterator);
								attachClassName(childIterator,classname);
							}
						}
						else
						{
							if (l2-i>0)
							{//if we are pointing to something too deep in the tree, we just select the whole parent
								highlightedNodes.push(childIterator);
								attachClassName(childIterator,classname);
								mathBlinkSetUp(childIterator,"right",findParentMRow(childIterator));
								mathStopBlinking();
							}
							else
							{//we are not too deep in the tree, we can afford to be picky
								if(rightNodeSide=="right")
								{//we ended on the right of rightNode, include it
									highlightedNodes.push(childIterator);
									attachClassName(childIterator,classname);
									mathBlinkSetUp(childIterator,"right",findParentMRow(childIterator));
									mathStopBlinking();
								}// else we ended on the left of rightNode, do not include it
								else
								{
									mathBlinkSetUp(childIterator,"left",findParentMRow(childIterator));
									mathStopBlinking();
								}
							}
						}
					}
				}
				else
				{
					mathBlinkSetUp(rightNode,"right",findParentMRow(rightNode));
					mathStopBlinking();
				}
			}

			return parentnode;
		}
	}
	return null;
}

function findNextSelectable(start)
{
	var currentNode=start.nextElementSibling,parentSearch=false;
	
	if (currentNode===null) 
	{
		currentNode=start.parentNode;
		parentSearch=true;
	}
	
	while (currentNode.nodeName!="math")
	{
		if ((currentNode.nodeName=="mrow")&&(/selectable/.test(currentNode.getAttribute('class'))))
		{//We found a selectable <mrow>
			if (currentNode.childElementCount<1)//Something wrong happened. This row is never supposed to be empty
			{
				insertEmptyElement(currentNode);
			}
			return currentNode;
		}
		else
		{
			if(parentSearch)
			{
				if (currentNode.nextElementSibling!=null)
				{
					currentNode=currentNode.nextElementSibling;
					parentSearch=false;
				}
				else 
				{
					currentNode=currentNode.parentNode;
				}
			}
			else if(currentNode.firstElementChild!=null)
			{
				currentNode=currentNode.firstElementChild;
			}
			else if (currentNode.nextElementSibling!=null)
			{
				currentNode=currentNode.nextElementSibling;
			}
			else 
			{
				currentNode=currentNode.parentNode;
				parentSearch=true;
			}
		}
	}
	
	return null;
}

function findLeveledNextSelectable(start)
{
	var currentNode=start.nextElementSibling,parentSearch=false,depth=1;
	
	if (currentNode===null) 
	{
		return null;
	}
	
	while (depth>0)
	{
		if ((currentNode.nodeName=="mrow")&&(/selectable/.test(currentNode.getAttribute('class'))))
		{//We found a selectable <mrow>
			if (currentNode.childElementCount<1)//Something wrong happened. This row is never supposed to be empty
			{
				insertEmptyElement(currentNode);
			}
			return currentNode;
		}
		else
		{
			if(parentSearch)
			{
				if (currentNode.nextElementSibling!=null)
				{
					currentNode=currentNode.nextElementSibling;
					parentSearch=false;
				}
				else 
				{
					currentNode=currentNode.parentNode;
					depth--;
				}
			}
			else if(currentNode.firstElementChild!=null)
			{
				currentNode=currentNode.firstElementChild;
				depth++;
			}
			else if (currentNode.nextElementSibling!=null)
			{
				currentNode=currentNode.nextElementSibling;
			}
			else 
			{
				currentNode=currentNode.parentNode;
				depth--;
				parentSearch=true;
			}
		}
	}
	
	return null;
}

function findPreviousSelectable(start)
{
	var currentNode=start.previousElementSibling,parentSearch=false;
	
	if (currentNode===null) 
	{
		currentNode=start.parentNode;
		parentSearch=true;
	}
	
	while (currentNode.nodeName!="math")
	{
		if ((currentNode.nodeName=="mrow")&&(/selectable/.test(currentNode.getAttribute('class'))))
		{//We found a selectable <mrow>
			if (currentNode.childElementCount<1)//Something wrong happened. This row is never supposed to be empty
			{
				insertEmptyElement(currentNode);
			}
			return currentNode;
		}
		else
		{
			if(parentSearch)
			{
				if (currentNode.previousElementSibling!=null)
				{
					currentNode=currentNode.previousElementSibling;
					parentSearch=false;
				}
				else 
				{
					currentNode=currentNode.parentNode;
				}
			}
			else if(currentNode.lastElementChild!=null)
			{
				currentNode=currentNode.lastElementChild;
			}
			else if (currentNode.previousElementSibling!=null)
			{
				currentNode=currentNode.previousElementSibling;
			}
			else 
			{
				currentNode=currentNode.parentNode;
				parentSearch=true;
			}
		}
	}
	
	return null;
}

function findLeveledPreviousSelectable(start)
{
	var currentNode=start.previousElementSibling,parentSearch=false,depth=1;
	
	if (currentNode===null) 
	{
		return null;
	}
	
	while (depth>0)
	{
		if ((currentNode.nodeName=="mrow")&&(/selectable/.test(currentNode.getAttribute('class'))))
		{//We found a selectable <mrow>
			if (currentNode.childElementCount<1)//Something wrong happened. This row is never supposed to be empty
			{
				insertEmptyElement(currentNode);
			}
			return currentNode;
		}
		else
		{
			if(parentSearch)
			{
				if (currentNode.previousElementSibling!=null)
				{
					currentNode=currentNode.previousElementSibling;
					parentSearch=false;
				}
				else 
				{
					currentNode=currentNode.parentNode;
					depth--;
				}
			}
			else if(currentNode.lastElementChild!=null)
			{
				currentNode=currentNode.lastElementChild;
				depth++;
			}
			else if (currentNode.previousElementSibling!=null)
			{
				currentNode=currentNode.previousElementSibling;
			}
			else 
			{
				currentNode=currentNode.parentNode;
				depth--;
				parentSearch=true;
			}
		}
	}
	
	return null;
}

function findParentMRow(node)
{
	for(var pnode=node.parentNode;((pnode!=null)&&(pnode.namespaceURI.indexOf("MathML") > -1));pnode=pnode.parentNode)
	{
		if ((pnode.nodeName==="mrow")&&(/selectable/.test(pnode.getAttribute('class')))) return pnode;
	}
	return null;
}

function getMathRoot(node)
{
	for(var pnode=node.parentNode;(pnode.namespaceURI.indexOf("MathML") > -1);pnode=pnode.parentNode)
	{
		if ((pnode.nodeName==="math")&&(/answerform/.test(pnode.getAttribute('class')))) return pnode;
	}
	return null;
}

function insertGreyedTimes(beforenode,classname)
{
//	<mpadded voffset="+.4em"> <mo class="greyedout" style="font-size:0.5em;	vertical-align:.8em !important;line-height:1.3em !important;"> x </mo></mpadded>
	var newMPaddingNode=document.createElementNS(mathMLnamespace,"mpadding"),
		newGreyedNode=document.createElementNS(mathMLnamespace,"mo"),
		newTextNode = document.createTextNode(greyedOutElements.times);

	newMPaddingNode.setAttribute('class',"greyedout single");//This <mpadded> will shift the times element up
	newGreyedNode.setAttribute('class',"greyedout");//This <mo> will show greyed out
	newGreyedNode.setAttribute('style',"font-size:0.5em;vertical-align:.4em !important;line-height:1.3em !important;");//Adjust the size and veritcal shift
	newGreyedNode.appendChild(newTextNode);
	newMPaddingNode.appendChild(newGreyedNode);
	
	attachClassName(newMPaddingNode,classname);
	
	beforenode.parentNode.insertBefore(newMPaddingNode,beforenode);
}


function setupEventListeners() {
	setUpSelectables();
	
	document.addEventListener('keyup',processKeyUp,false);
	document.addEventListener('keydown',processKeyDown,false);
	document.addEventListener('keypress',processKeyPress,false);

	document.addEventListener('click',clickListener,true);
}

function processKeyUp(evt)
{
	if((mathBlinkingOn)&&(mathBlinkingObj.obj!=null)&&(mathBlinkingObj.obj.parentNode!=null)) //If there is a clicked math object
	{
		if ((!mathHighlightingInProgress)&&(!mathDraggingOn)&&(mathBlinkingObj.obj.namespaceURI.indexOf("MathML") > -1))//Just paranoid here
		{
			var obj=mathBlinkingObj.obj, 
				objParent=obj.parentNode;
				
			if(!((evt.ctrlKey)||(evt.altKey)||(evt.metaKey)))//Let the browsers deal with keystrokes
			{
			
			// Some book keeping needs to be done. Sequences of digits have to be put into mrows
			// Functions need to be recognized at this step
				if (/selectable/.test(objParent.getAttribute('class')))
				{
					breakAndJoinMRows(objParent)
				}
				else
				{
					breakAndJoinMRows(findParentMRow(objParent))
				}
				
			}
		}
		if (evt. preventDefault) evt.preventDefault();
	}
}

function processKeyDown(evt)
//This function processes non-characted input, such as arrows pressed, backspace etc
{
	if((mathBlinkingOn)&&(mathBlinkingObj.obj!=null)&&(mathBlinkingObj.obj.parentNode!=null)) //If there is a clicked math object
	{
		if ((!mathHighlightingInProgress)&&(!mathDraggingOn)&&(mathBlinkingObj.obj.namespaceURI.indexOf("MathML") > -1))//Just paranoid here
		{
			var obj=mathBlinkingObj.obj, 
				objParent=obj.parentNode, 
				code=evt.keyCode,//which button was pressed
				emptyContainerCheck=false;//If this is true, then we just altered an empty placeholder
			
			if(!((evt.ctrlKey)||(evt.altKey)||(evt.metaKey)))//Let the browsers deal with keystrokes
			{
				var targetSibling=obj; //This is the object that has cursor
				
				if (code==8) //Backspace. TODO: Highlight complex objects before deletion, handle msup/msub nodes sequentially
				{
					if (!(/empty/.test(objParent.getAttribute('class')))) //Don't do anything if we are empty already
					{
						if (mathBlinkingObj.side==="left")//Delete previous sibling
						{
							if (targetSibling.previousElementSibling!=null)
							{//there is something to delete
								removeLast(targetSibling.previousElementSibling,false,false);
							}
							else
							{//there is nothing to delete at the current level. Do nothing for now. TODO move to previous selectable and start deleting there
								while ((objParent.previousElementSibling==null)&&(!(/selectable/.test(objParent.getAttribute('class')))))
								{
									objParent=objParent.parentNode;
								}
								if (objParent.previousElementSibling)
								{
									removeLast(objParent.previousElementSibling,false,false);
								}
							}
						}
						else//Delete the cursor holding object, check if we become empty
						{
							removeLast(targetSibling,true,false);
						}
					}
				}
				else if (code==39) //Right arrow. TODO: process arrows with modifiers, like [shift] + [->] or [Ctrl] + [->]
				{
					if (mathBlinkingObj.side==="left")//Element blinks on the left, move blinking to the right if a simple object. If not, move inside
					{
						focusInFromLeft(obj,true);
					}
					else //Element blinks on the right, move blinking to the nextSibling make it blink on the right
					{
						focusOutToRight(obj);
					}
				}
				else if (code==37) //Left arrow. TODO: process arrows with modifiers, like [shift] + [<-] or [Ctrl] + [<-]
				{
					if (mathBlinkingObj.side==="right")//Element blinks on the right, move blinking to the left if a simple object. If not, move inside
					{
						focusInFromRight(obj,true);
					}
					else //Element blinks on the left, move blinking to the previousSibling make it blink on the left
					{
						focusOutToLeft(obj);
					}
				}
				else if (code==9) //Tab key
				{
					if (evt.shiftKey)
					{
						if (/selectable/.test(objParent.getAttribute('class')))
						{//We reached the begining of the selectable <mrow>, jump out of it
							jumpOutToLeft(obj,objParent);
						}
						else //get out of the shell and look again
						{
							jumpOutToLeft(objParent,objParent.parentNode);
						}
					}
					else
					{
						if (/selectable/.test(objParent.getAttribute('class')))
						{//We reached the begining of the selectable <mrow>, jump out of it
							jumpOutToRight(obj,objParent);
						}
						else //get out of the shell and look again
						{
							jumpOutToRight(objParent,objParent.parentNode);
						}
					}
				}
				else if (code==13) //Enter key
				{
					if (/selectable/.test(objParent.getAttribute('class')))
					{//We reached the begining of the selectable <mrow>, jump out of it
						jumpOutToRight(obj,objParent);
					}
					else //get out of the shell and look again
					{
						jumpOutToRight(objParent,objParent.parentNode);
					}
				}
			}
			if (evt. preventDefault) evt.preventDefault();
		}
	}
}

function removeEmptyElement(targetParent)
{
	var MPhantom=targetParent.firstElementChild;
	while (MPhantom!=null) 
	{
		if(MPhantom.nodeName=="mphantom")
		{
			targetParent.removeChild(MPhantom);
			break;
		}
		MPhantom=MPhantom.nextElementSibling;
	}
}

function createEmptyElement()
{
	var newMRow=document.createElementNS(mathMLnamespace,"mrow"),//Create a set of objects that will represent an empty placeholder
		newMPhantom=document.createElementNS(mathMLnamespace,"mphantom"),
		newTextNode = document.createTextNode("X");
	
	newMRow.setAttribute('class',"selectable empty")
	
	newMPhantom.appendChild(newTextNode);
	newMRow.appendChild(newMPhantom);
	
	attachClassName(newMRow,'mathmovable');
	attachClassName(newMPhantom,'mathmovable');
	
	return newMRow;
}

function insertEmptyElement(targetParent)
{
	var newMPhantom=document.createElementNS(mathMLnamespace,"mphantom"),//Create a set of objects that will represent an empty placeholder
		newTextNode = document.createTextNode("X");
	
	var classstring = targetParent.getAttribute('class'); 
	if (classstring==null) classstring="";
	if (!(/empty/.test(classstring))) targetParent.setAttribute('class',classstring + " empty");
	
	newMPhantom.appendChild(newTextNode);
	attachClassName(newMPhantom,'mathmovable');
	targetParent.appendChild(newMPhantom);
}

function getMathElementsByClassName(root,classselector)
{
	var elementList=[],patt=RegExp(classselector);
	var children=root.children,item,classname;
	var subList;
	
	for (var i=0;i<children.length;i++)
	{
		item=children[i];
		
		if (item.getAttribute)
		{
			classname =item.getAttribute('class');
			if(patt.test(classname))
			{
				elementList.push(item);
			}
		}
		
		if (item.childElementCount>0) 
		{
			subList=getMathElementsByClassName(item,classselector);
		
			for (var j=0;j<subList.length;j++) 
			{
				elementList.push(subList[j]);
			}
		}
	}
	return elementList;
}

function getMathElementsByTagName(root,tagselector)
{
	var elementList=[],patt=RegExp(tagselector);
	var children=root.children,item,tagname;
	var subList;
	
	for (var i=0;i<children.length;i++)
	{
		item=children[i];
		
		if (item.getAttribute)
		{
			tagname =item.nodeName;
			if(patt.test(tagname))
			{
				elementList.push(item);
			}
		}
		
		if (item.childElementCount>0) 
		{
			subList = getMathElementsByTagName(item,tagselector);
		
			for (var j=0;j<subList.length;j++) 
			{
				elementList.push(subList[j]);
			}
		}
	}
	return elementList;
}

function wrapElementsInMRows(root,tagname)
{
	var elementlist=getMathElementsByTagName(root,tagname),item,
		patt = new RegExp('homogeneous'+tagname+'row'),
		isTag = new RegExp(tagname);
	
	for (var i=0;i<elementlist.length;i++)
	{
		item = elementlist[i];
		
		if (patt.test(item.parentNode.getAttribute('class')))
		{
			item = item.parentNode;
			while((item.nextElementSibling)&&(isTag.test(item.nextElementSibling.nodeName)))
			{
				item.appendChild(item.nextElementSibling);
			}
		}
		else //create an <mrow> and put numbers in it.
		{
			var newhomogeneousmnrow=document.createElementNS(mathMLnamespace,"mrow");
			
			newhomogeneousmnrow.setAttribute('class','homogeneous'+tagname+'row');

			item.parentNode.insertBefore(newhomogeneousmnrow,item);
			newhomogeneousmnrow.appendChild(item);
			item = item.parentNode;
			while((item.nextElementSibling)&&(/mn/.test(item.nextElementSibling.nodeName)))
			{
				item.appendChild(item.nextElementSibling);
			}
		}
		
	}
}


function joinMRows(root,classname)
{
	var	mrowlist=getMathElementsByClassName(root,classname),item,
		patt= new RegExp(classname);
	
	for (var i=0;i<mrowlist.length;i++)
	{
		item = mrowlist[i];
		if (item.nextElementSibling)
		{
			if (patt.test(item.nextElementSibling.getAttribute('class')))
			{
				var sourceMRow=item.nextElementSibling;
				
				for (var j=0;j<sourceMRow.childElementCount;i++)
				{
					item.appendChild(sourceMRow.children[j]);
				}
				sourceMRow.parentNode.removeChild(sourceMRow);
				
				return;
			}
		}
	}
}

function analyzeMRow(mrowNode)
{
	var elementName,classname=mrowNode.getAttribute('class'),
		childElements=mrowNode.children;
	
	if ((classname!=null)&&(/homogeneous\w*row/.test(classname)))
	{
		elementName = classname.replace(/^.*homogeneous/,"").replace(/row.*$/,"")
		
		for (var i=0;i<mrowNode.childElementCount;i++)
		{
			if (childElements[i].nodeName!=elementName)
			{
				breakMRow(childElements[i],EXTRACTNODE);
				i=-1;
			}
		}
		if (mrowNode.childElementCount<1)
		{//Zap an empty <homogeneous mrow>
			mrowNode.parentNode.removeChild(mrowNode);
		}
	}
}

function wrapAmbiguousFormsInParenthesis(root)
{
	var rowslist=getMathElementsByTagName(root,"mrow"),parentname;
	
	for (var i=0;i<rowslist.length;i++)// Wrapping
	{
		var row=rowslist[i], parentnode=row.parentNode;
		parentname=parentnode.nodeName;
		
		if(parentname=="msup") 
		{//A row is in one of those. Check if it is a firstElemtnChild
			if (row==parentnode.firstElementChild)
			{//It is a first elemnt, chick if it is complex
				if ((row.childElementCount>1)||(/(mfrac|msup|msubsup)/.test(row.firstElementChild.nodeName)))
				{//Wrap in greyed out parenthesis
					var newMFenced=document.createElementNS(mathMLnamespace,"mfenced");
					
					newMFenced.setAttribute('class',"greyedout");
					newMFenced.setAttribute('open',"(");
					newMFenced.setAttribute('close',")");
					
					parentnode.insertBefore(newMFenced,row);
					newMFenced.appendChild(row);
				}
			}
		}
		else if ((parentname=="mfenced")&&(/greyedout/.test(parentnode.getAttribute('class'))))
		{
			if (row==parentnode.firstElementChild)
			{//It is a first elemnt, chick if it is complex
				if ((row.childElementCount<2)&&(!(/(mfrac|msup|msubsup)/.test(row.firstElementChild.nodeName))))
				{//Remove greyed out parenthesis
					var topparent=parentnode.parentNode;
					
					topparent.insertBefore(row,parentnode);
					topparent.removeChild(parentnode);
				}
			}

		}
	}	
}

function insertGreyedElements(root)
{
	var mspaceList=getMathElementsByTagName(root,"mspace");
	var msubList=getMathElementsByTagName(root,"msub");
	var msupList=getMathElementsByTagName(root,"msup");
	var msubsupList=getMathElementsByTagName(root,"msubsup");
	var grayedTimesList=getMathElementsByClassName(root,"inreplacementofmspace");
	var dotList=getMathElementsByClassName(root,"decimaldot");
	
	var mElem,i;
	
	for (i=0;i<mspaceList.length;i++)// Converting spaces into greyed out <times>
	{
		mElem=mspaceList[i];
		
		if(mElem.nextElementSibling)
			if(mElem.previousElementSibling)
				if (((/homogeneous\w*row/.test(mElem.nextElementSibling.getAttribute('class')))||(mElem.nextElementSibling.nodeName=="mn"))&&
						((/homogeneous\w*row/.test(mElem.previousElementSibling.getAttribute('class')))||(mElem.previousElementSibling.nodeName=="mn")))
				{//Convert
					insertGreyedTimes(mElem,"inreplacementofmspace mathmovable");
					
					if(mathBlinkingObj.obj==mElem)
					{//This space had focus on it
						if(mathBlinkingObj.side=="left")
						{
							mathBlinkSetUp(mElem.previousElementSibling,"right",null);
						}
						else
						{
							mathBlinkSetUp(mElem.nextElementSibling,"left",null);
						}
					}
					
					mElem.parentNode.removeChild(mElem);
				}
	}
	
	for (i=0;i<msupList.length;i++)
	{//insert greyed out times if we have a number followed by a sub- supscript element that starts with a number
		mElem=msupList[i];

		if(mElem.previousElementSibling)
			if((/homogeneousmnrow/.test(mElem.firstElementChild.firstElementChild.getAttribute('class')))||(mElem.firstElementChild.firstElementChild.nodeName=="mn"))
				if((/homogeneousmnrow/.test(mElem.previousElementSibling.getAttribute('class')))||(mElem.previousElementSibling.nodeName=="mn"))
				{//Convert
					insertGreyedTimes(mElem,"separatenumbersvisually");
				}
	}
	
	for (i=0;i<grayedTimesList.length;i++)// check to see if we still need greyed out <times>
	{
		mElem=grayedTimesList[i];
		
		if ((mElem.nextElementSibling==null)||(mElem.nextElementSibling==null)||(!((/homogeneous\w*row/.test(mElem.nextElementSibling.getAttribute('class')))||(mElem.nextElementSibling.nodeName=="mn")))||(!((/homogeneous\w*row/.test(mElem.previousElementSibling.getAttribute('class')))||(mElem.previousElementSibling.nodeName=="mn"))))
		{
			var template = {preformatted:false,nodename:"mspace",textdata:"",nodeattributes:[["width","mediummathspace"]]};
			addLeft(mElem,template);
			mathBlinkSetUp(mElem.previousElementSibling,"right",null);
			mElem.parentNode.removeChild(mElem);
		}
	}
	
}

function breakAndJoinMRows(root)
{
	var rowslist=getMathElementsByTagName(root,"mrow"),item,classname,tagname,
		blinkobj = mathBlinkingObj.obj,blinkside = mathBlinkingObj.side,blinkrow = mathBlinkingObj.selectedMrow;
		
	mathStopBlinking();// turn blinking temporarily off;
	
	for (var i=0;i<rowslist.length;i++)// Breaking stage
	{
		classname=rowslist[i].getAttribute('class');
		
		if(/selectable/.test(classname)) 
		{
			//Do nothing. We want to exclude these from being joined
		}
		else if (/functionname/.test(classname))
		{
			//Analyze if the function names are still intact
		}
		else if (/homogeneous\w*row/.test(classname))
		{
			//This is a container <mrow> that is supposed to hold atomic items of the same kind
			analyzeMRow(rowslist[i]);
		}
	}
	
	for (var i=0;i<joinableTags.length;i++)//Wrapping stage
	{
		tagname=joinableTags[i];
		wrapElementsInMRows(root,tagname) 
	}
	
	for (var i=0;i<joinableTags.length;i++)//Joining stage
	{
		tagname=joinableTags[i];
		classname='homogeneous'+tagname+'row';
		joinMRows(root,classname);
	}
	
	wrapAmbiguousFormsInParenthesis(root.parentNode); //If there is a complex object with operation applied to it, wrap it in parenthesis.
	
	item = findParentMRow(root.parentNode);
	
	if (item)
	{
		insertGreyedElements(item);
	}
	else
	{
		insertGreyedElements(root.parentNode);
	}
	
	mathResumeBlinking();
}

function breakMRow(atNode,separate)
{
	if(atNode!=null)
	{
		var newNode=document.createElementNS(mathMLnamespace,"mrow");//create a new container mrow
		var	objParent=atNode.parentNode;	
		
		newNode.setAttribute('class', atNode.parentNode.getAttribute('class'));//This mrow will contain specific atomic elements only.
	
		for (var node=atNode.previousElementSibling;node!=null;node=atNode.previousElementSibling)//Go through all siblings that are to the left and move them to the newly created mrow
		{
			newNode.insertBefore(node,newNode.firstChild);
		}
		if (separate)
		{
			objParent.parentNode.insertBefore(atNode,objParent);//Inserte the offending element in front of the original <mrow>
		}
		else
		{
			atNode=objParent;
		}
		if (newNode.childElementCount>0) 
		{
			objParent.parentNode.insertBefore(newNode,atNode);//Insert the new <mrow> before the curent <mrow>
			return newNode;
		}
		
		newNode=null;
	}
	return null;
}

function addLeft(target,template)
/* template = 
		{
			preformatted:true/false,
			nodedata: if preformatted
			nodename: mn mi mo mspace msup msub msubsup mfenced,
			nodeattributes:{style,class,open,close}
			textdata: ,
			numberofchildren:# 0 if a simple item,
			consumetarget: true/false,//needed to specify is the target elemen will be inside the template
			targetplaceholderindex:#
			targetplaceholdernode: if preformatted
		}
*/
{
	if((target)&&(target.parentNode)&&(template.preformatted!=null))// If data is supplied correctly
	{
		var targetParent=target.parentNode,emptyContainerCheck=false;
				
		if (template.preformatted)
		{//template is ready for insertion
		}
		else
		{//have to format the nodes
			switch(template.nodename)
			{
			case "mi":
			case "mn":
			case "mo":
				var newNode=document.createElementNS(mathMLnamespace,template.nodename),
					newTextNode = document.createTextNode(template.textdata);
					
				if(template.nodeattributes)
				{
					for (var i=0;i<template.nodeattributes.length;i++)
					{
						newNode.setAttribute(template.nodeattributes[i][0],template.nodeattributes[i][1]);
					}
				}

				if(/empty/.test(targetParent.getAttribute('class')))
				{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
					emptyContainerCheck=true;
					
					targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
				}
				
				newNode.appendChild(newTextNode);
				attachClassName(newNode,'mathmovable');
				targetParent.insertBefore(newNode,target);
				if (emptyContainerCheck)
				{
					mathBlinkSetUp(newNode,"right",null);
				}
				break;
			case "msup":
				if((template.consumetarget)&&(template.consumetarget==true))
				{	
					//Treat target as a simple element. If it is not, then prosessKeyUp will wrap it in greyed out parenthesis if needed. We have to analyze the container, in case we need to break it apart.
					//We need to make sure we are not in some sort of container <mrow>
					if(/selectable/.test(targetParent.getAttribute('class')))
					{//Everything is fine. Just insert the template.
						var newNode=document.createElementNS(mathMLnamespace,template.nodename),
							base=document.createElementNS(mathMLnamespace,"mrow"),exponent=createEmptyElement();
						
						base.setAttribute('class',targetParent.getAttribute('class').replace(/\s*selected\s*/," "));
						attachClassName(base,'mathmovable');
						
						if(/empty/.test(targetParent.getAttribute('class')))
						{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
							emptyContainerCheck=true;
							
							targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
						}
						
						newNode.appendChild(base);
						newNode.appendChild(exponent);

						attachClassName(newNode,'mathmovable');
						targetParent.insertBefore(newNode,target);
						
						newNode.firstElementChild.appendChild(target);
						
						mathBlinkSetUp(exponent.firstElementChild,"left",exponent);							
					}
					else
					{//Break the row and inset the template after the first piece
						if(target.nextElementSibling)
						{//Not the last element of the row
							addLeft(breakMRow(target.nextElementSibling,false),template);
						}
						else
						{//Last element of the row
							addLeft(targetParent,template);
						}
					}
				}
				else
				{// Insert a template with two empty placeholders
					var newNode=document.createElementNS(mathMLnamespace,template.nodename),
					base=createEmptyElement(),exponent=createEmptyElement();
					
					newNode.appendChild(base);
					newNode.appendChild(exponent);
					
					attachClassName(newNode,'mathmovable');
					
					targetParent.insertBefore(newNode,target);
					mathBlinkSetUp(base.firstElementChild,"left",base);
				}
				
				break;
			case "mfrac":
				if((template.consumetarget)&&(template.consumetarget==true))
				{	
					//Just shove the target item into the numerator
					//We need to make sure we are not in some sort of container <mrow>
					if(/selectable/.test(targetParent.getAttribute('class')))
					{//Everything is fine. Just insert the template.
						var newNode=document.createElementNS(mathMLnamespace,template.nodename),
							numerator=document.createElementNS(mathMLnamespace,"mrow"),denominator=createEmptyElement();
						
						numerator.setAttribute('class',targetParent.getAttribute('class').replace(/\s*selected\s*/," "));
						attachClassName(numerator,'mathmovable');
						
						if(/empty/.test(targetParent.getAttribute('class')))
						{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
							emptyContainerCheck=true;
							
							targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
						}
						
						newNode.appendChild(numerator);
						newNode.appendChild(denominator);
						
						attachClassName(newNode,'mathmovable');

						targetParent.insertBefore(newNode,target);
						
						numerator.appendChild(target);
						
						mathBlinkSetUp(denominator.firstElementChild,"left",denominator);							
					}
					else
					{//Break the row and inset the template after the first piece
						if(target.nextElementSibling)
						{//Not the last element of the row
							addLeft(breakMRow(target.nextElementSibling,false),template);
						}
						else
						{//Last element of the row
							addLeft(targetParent,template);
						}
					}
				}
				else
				{// Insert a template with two empty placeholders
					var newNode=document.createElementNS(mathMLnamespace,template.nodename),
					numerator=createEmptyElement(),denominator=createEmptyElement();
					
					newNode.appendChild(numerator);
					newNode.appendChild(denominator);
					
					attachClassName(newNode,'mathmovable');
					
					targetParent.insertBefore(newNode,target);
					mathBlinkSetUp(numerator.firstElementChild,"left",numerator);
				}
				
				break;
			case "mspace":
				var newNode=document.createElementNS(mathMLnamespace,template.nodename);
				
				if(template.nodeattributes)
				{
					for (var i=0;i<template.nodeattributes.length;i++)
					{
						newNode.setAttribute(template.nodeattributes[i][0],template.nodeattributes[i][1]);
					}
				}
				
				if (!(/empty/.test(targetParent.getAttribute('class'))))
				{//We are adding stuff to a non-empty placeholder
					attachClassName(newNode,'mathmovable');
					targetParent.insertBefore(newNode,target);
				}
				break;
			case "mfenced":
				var newNode=document.createElementNS(mathMLnamespace,"mfenced"), //Create a set of objects that will represent an empty placeholder
					inside=createEmptyElement();

				if(/empty/.test(targetParent.getAttribute('class')))
				{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
					emptyContainerCheck=true;
					
					targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
				}
				if(template.nodeattributes)
				{
					for (var i=0;i<template.nodeattributes.length;i++)
					{
						newNode.setAttribute(template.nodeattributes[i][0],template.nodeattributes[i][1]);
					}
				}
				
				attachClassName(newNode,'mathmovable');
				
				newNode.appendChild(inside);
				targetParent.insertBefore(newNode,target);
				mathBlinkSetUp(inside.firstElementChild,"left",inside);	
				break;
			}
		}
		
		if (emptyContainerCheck)
		{
			removeEmptyElement(targetParent);
		}
	}
}

function addRight(target,template)
/* template = 
		{
			preformatted:true/false,
			nodedata: if preformatted
			nodename: mn mi mo mspace msup msub msubsup mfenced,
			nodeattributes:{style,class,open,close}
			textdata: ,
			numberofchildren:# 0 if a simple item,
			consumetarget: true/false,//needed to specify is the target elemen will be inside the template
			targetplaceholderindex:#
			targetplaceholdernode: if preformatted
		}
*/
{
	if((target)&&(target.parentNode)&&(template.preformatted!=null))// If data is supplied correctly
	{
		var targetParent=target.parentNode,emptyContainerCheck=false;
				
		if (template.preformatted)
		{//template is ready for insertion
		}
		else
		{//have to format the nodes
			switch(template.nodename)
			{
			case "mi":
			case "mn":
			case "mo":
				var newNode=document.createElementNS(mathMLnamespace,template.nodename),
					newTextNode = document.createTextNode(template.textdata);
					
				if(template.nodeattributes)
				{
					for (var i=0;i<template.nodeattributes.length;i++)
					{
						newNode.setAttribute(template.nodeattributes[i][0],template.nodeattributes[i][1]);
					}
				}

				if(/empty/.test(targetParent.getAttribute('class')))
				{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
					emptyContainerCheck=true;
					
					targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
				}
				
				newNode.appendChild(newTextNode);
				attachClassName(newNode,'mathmovable');
				if (target.nextElementSibling)
				{
					targetParent.insertBefore(newNode,target.nextElementSibling);
				}
				else
				{
					targetParent.appendChild(newNode);
				}
				mathBlinkSetUp(newNode,"right",null);
				break;
			case "msup":
				if((template.consumetarget)&&(template.consumetarget==true))
				{	
					//Treat target as a simple element. If it is not, then prosessKeyUp will wrap it in greyed out parenthesis if needed. We have to analyze the container, in case we need to break it apart.
					//We need to make sure we are not in some sort of container <mrow>
					if(/selectable/.test(targetParent.getAttribute('class')))
					{//Everything is fine. Just insert the template.
						var newNode=document.createElementNS(mathMLnamespace,template.nodename),
							base=document.createElementNS(mathMLnamespace,"mrow"),exponent=createEmptyElement();
						
						base.setAttribute('class',targetParent.getAttribute('class').replace(/\s*selected\s*/," "));
						
						if(/empty/.test(targetParent.getAttribute('class')))
						{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
							emptyContainerCheck=true;
							
							targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
						}
						
						newNode.appendChild(base);
						newNode.appendChild(exponent);
						attachClassName(newNode,'mathmovable');
						attachClassName(base,'mathmovable');

						targetParent.insertBefore(newNode,target);
						
						newNode.firstElementChild.appendChild(target);
						
						mathBlinkSetUp(exponent.firstElementChild,"left",exponent);							
					}
					else
					{//Break the row and inset the template after the first piece
						if(target.nextElementSibling)
						{//Not the last element of the row
							breakMRow(target.nextElementSibling,false);
							addLeft(targetParent,template);
						}
						else
						{//Last element of the row
							addLeft(targetParent,template);
						}
					}
				}
				else
				{// Insert a template with two empty placeholders
					var newNode=document.createElementNS(mathMLnamespace,template.nodename),
					base=createEmptyElement(),exponent=createEmptyElement();
					
					newNode.appendChild(base);
					newNode.appendChild(exponent);
					attachClassName(newNode,'mathmovable');
					
					if(target.nextElementSibling)
					{
						targetParent.insertBefore(newNode,target.nextElementSibling);
					}
					else
					{
						targetParent.appenChild(newNode);
					}
					mathBlinkSetUp(base.firstElementChild,"left",base);
				}
				
				break;
			case "mfrac":
				if((template.consumetarget)&&(template.consumetarget==true))
				{	
					//Just shove the target item into the numerator
					//We need to make sure we are not in some sort of container <mrow>
					if(/selectable/.test(targetParent.getAttribute('class')))
					{//Everything is fine. Just insert the template.
						var newNode=document.createElementNS(mathMLnamespace,template.nodename),
							numerator=document.createElementNS(mathMLnamespace,"mrow"),denominator=createEmptyElement();
						
						numerator.setAttribute('class',targetParent.getAttribute('class').replace(/\s*selected\s*/," "));
						
						if(/empty/.test(targetParent.getAttribute('class')))
						{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
							emptyContainerCheck=true;
							
							targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
						}
						
						newNode.appendChild(numerator);
						newNode.appendChild(denominator);
						attachClassName(newNode,'mathmovable');
						attachClassName(numerator,'mathmovable');

						targetParent.insertBefore(newNode,target);
						
						numerator.appendChild(target);
						
						mathBlinkSetUp(denominator.firstElementChild,"left",denominator);							
					}
					else
					{//Break the row and inset the template after the first piece
						if(target.nextElementSibling)
						{//Not the last element of the row
							addLeft(breakMRow(target.nextElementSibling,false),template);
						}
						else
						{//Last element of the row
							addLeft(targetParent,template);
						}
					}
				}
				else
				{// Insert a template with two empty placeholders
					var newNode=document.createElementNS(mathMLnamespace,template.nodename),
					numerator=createEmptyElement(),denominator=createEmptyElement();
					
					newNode.appendChild(numerator);
					newNode.appendChild(denominator);
					attachClassName(newNode,'mathmovable');
					
					targetParent.insertBefore(newNode,target);
					mathBlinkSetUp(numerator.firstElementChild,"left",numerator);
				}
				
				break;
			case "mspace":
				var newNode=document.createElementNS(mathMLnamespace,template.nodename);
				
				if(template.nodeattributes)
				{
				for (var i=0;i<template.nodeattributes.length;i++)
					{
						newNode.setAttribute(template.nodeattributes[i][0],template.nodeattributes[i][1]);
					}
				}
				attachClassName(newNode,'mathmovable');
				
				if (!(/empty/.test(targetParent.getAttribute('class'))))
				{//We are adding stuff to a non-empty placeholder
					if(target.nextElementSibling)
					{
						targetParent.insertBefore(newNode,target.nextElementSibling);
					}
					else
					{
						targetParent.appendChild(newNode);
					}
				}
				mathBlinkSetUp(newNode,"right",null);
				break;
			case "mfenced":
				var newNode=document.createElementNS(mathMLnamespace,"mfenced"), //Create a set of objects that will represent an empty placeholder
					inside=createEmptyElement();

				if(/empty/.test(targetParent.getAttribute('class')))
				{//We are adding stuff to an empty placeholder, process as we usually do, then clean up later
					emptyContainerCheck=true;
					
					targetParent.setAttribute('class',targetParent.getAttribute('class').replace(/\s*empty/,""));
				}
				if(template.nodeattributes)
				{
					for (var i=0;i<template.nodeattributes.length;i++)
					{
						newNode.setAttribute(template.nodeattributes[i][0],template.nodeattributes[i][1]);
					}
				}
				
				attachClassName(newNode,'mathmovable');
				
				newNode.appendChild(inside);
				if(target.nextElementSibling)
				{
					targetParent.insertBefore(newNode,target.nextElementSibling);
				}
				else
				{
					targetParent.appendChild(newNode);
				}
				mathBlinkSetUp(inside.firstElementChild,"left",inside);	
				break;
			}
		}
		
		if (emptyContainerCheck)
		{
			removeEmptyElement(targetParent);
		}
	}
}

function jumpOutToRight(target,targetParent)
{
	var focusReceiver=null,
		receiveside="left"/*on which side the focusReceiver gets the focus, left by default*/,
		selectedmrow=mathBlinkingObj.selectedMrow;
	
	mathStopBlinking();
	
	if(/greyedout/.test(targetParent.parentNode.getAttribute('class')))
	{//If the selectable <mrow> is wrapped by greyed out element, look for siblings of greyed out element
		focusReceiver = findLeveledNextSelectable(targetParent.parentNode);//Find any selectable fields that are on the same branch
	}
	else
	{
		focusReceiver = findLeveledNextSelectable(targetParent);//Find any selectable fields that are on the same branch
	}
	
	if(focusReceiver!=null)
	{//We are in the middle of a template that still has places to go
		focusInFromLeft(focusReceiver,false);
	}
	else
	{//We also ran out of places to go within the template we live in
		selectedmrow=findParentMRow(targetParent);
		if (selectedmrow!=null)
		{//There is life out there, jump out of the template and give it focus on the right
			focusReceiver=targetParent.parentNode;
			
			while ((focusReceiver.nodeName=="mrow")||(/greyedout/.test(focusReceiver.getAttribute('class')))) focusReceiver=focusReceiver.parentNode;
			receiveside="right";
			mathBlinkSetUp(focusReceiver,receiveside,selectedmrow);
		}
		else
		{//There is nothing above us. We try to search for other <selectable mrows> further down the tree
			selectedmrow=findNextSelectable(targetParent);
			focusInFromLeft(selectedmrow,false);
		}
	}
	mathResumeBlinking();
}

function focusOutToRight(target) //Object tries to find someone to rely focus to
{
	if((target)&&(target.parentNode)&&(target.namespaceURI.indexOf("MathML") > -1))// If data is supplied correctly
	{
		mathStopBlinking();
		var targetParent=target.parentNode,focusReceiver=null,/*This is an object to receive the focus*/
			receiveside="left"/*on which side the focusReceiver gets the focus, left by default*/,
			selectedmrow=mathBlinkingObj.selectedMrow;
		
		if(target.nextElementSibling!=null)
		{//There are elements on the right. Rely focus to them
			focusInFromLeft(target.nextElementSibling,false);
		}
		else//There are no elements to the right. We must look further down the tree.
		{
			if (/selectable/.test(targetParent.getAttribute('class')))
			{//We reached the end of the selectable <mrow>, jump out of it
				jumpOutToRight(target,targetParent);
			} 
			else //get out of the shell and look again
			{
				focusOutToRight(targetParent);
			}
		}
		
		mathResumeBlinking();
	}
}

function focusInFromLeft(target,
					self /*Does the object try to move focus around itself, or is it its leftSibling? true or false*/
					)
//Focus moves to the right
{
	if((target)&&(target.parentNode))// If data is supplied correctly
	{
		mathStopBlinking();
		var targetParent=target.parentNode,focusReceiver=null,/*This is an object to receive the focus*/
			receiveside="left"/*on which side the focusReceiver gets the focus, left by default*/,
			selectedmrow=mathBlinkingObj.selectedMrow;
			if (/empty/.test(targetParent.getAttribute('class')))//We are in the empty placeholder move to the next meaningful element
			{
				if (self)
				{//Empty placeholder tries to move focus around itself. We should get out of it and find other target
					focusOutToRight(target);
				}
				else
				{//Empty placeholder receives focus from someone on the left
					selectedmrow=targetParent;
					focusReceiver=selectedmrow.firstElementChild;
				}
			}
			else//We are in the nonempty container
			{
				if ((self)&&(self==true)) //The object tries to shuffle focus around itself
				{
					if ((target.childElementCount>0)&&(!(/single/.test(target.getAttribute('class')))))
					{//Composite element. Move inside
						if (/selectable/.test(target.firstElementChild.getAttribute('class')))
						{//Element contains its own input fields
							selectedmrow=target.firstElementChild;
							focusReceiver=selectedmrow.firstElementChild;
						}
						else if (/greyedout/.test(target.firstElementChild.getAttribute('class')))
						{//Element has a complicated structure, let it deal with it
							focusInFromLeft(target.firstElementChild,false);
						}
						else
						{//Element has a complicated structure, let it deal with it
							focusInFromLeft(target.firstElementChild,false);
						}
					}
					else //Simple element
					{
						if (/greyedout/.test(target.getAttribute('class')))
						{
							if(target.nextElementSibling!=null)
							{
								focusReceiver=target.nextElementSibling
							}
							else focusOutToRight(target);
						}
						else
						{
							focusReceiver=target;
							receiveside="right";
						}
					}
				}
				else //Someone from the left tries to rely focus to us
				{
					if ((target.childElementCount>0)&&(!(/single/.test(target.getAttribute('class')))))
					{//Composite element. Move inside
						if (/selectable/.test(target.getAttribute('class')))
						{//Element contains its own input fields
							selectedmrow=target;
							focusReceiver=selectedmrow.firstElementChild;
						}
						else if (/greyedout/.test(target.getAttribute('class')))
						{//Element has a complicated structure, let it deal with it
							focusInFromLeft(target.firstElementChild,false);
						}
						else
						{//We move focus to the first child
							focusInFromLeft(target.firstElementChild,false);
						}
					}
					else
					{
						if (/greyedout/.test(target.getAttribute('class')))
						{
							if(target.nextElementSibling!=null)
							{
								focusReceiver=target.nextElementSibling
							}
							else focusOutToRight(target);
						}
						else
						{
							focusReceiver=target;
							receiveside="right";
						}
					}
				}
			}
		if (focusReceiver!=null)
		{
			mathBlinkSetUp(focusReceiver,receiveside,selectedmrow);
		}
		else
		{
			mathResumeBlinking();
		}
	}
}

function jumpOutToLeft(target,targetParent)
{
	var focusReceiver=null,
		receiveside="right"/*on which side the focusReceiver gets the focus, left by default*/,
		selectedmrow=mathBlinkingObj.selectedMrow;

	mathStopBlinking();

	if(/greyedout/.test(targetParent.parentNode.getAttribute('class')))
	{//If the selectable <mrow> is wrapped by greyed out element, look for siblings of greyed out element
		focusReceiver = findLeveledPreviousSelectable(targetParent.parentNode);//Find any selectable fields that are on the same branch
	}
	else
	{
		focusReceiver = findLeveledPreviousSelectable(targetParent);//Find any selectable fields that are on the same branch
	}
	
	if(focusReceiver!=null)
	{//We are in the middle of a template that still has places to go
		focusInFromRight(focusReceiver,false);
	}
	else
	{//We also ran out of places to go within the template we live in
		selectedmrow=findParentMRow(targetParent);
		if (selectedmrow!=null)
		{//There is life out there, jump out of the template and give it focus on the right
			focusReceiver=targetParent.parentNode;
			
			while ((focusReceiver.nodeName=="mrow")||(/greyedout/.test(focusReceiver.getAttribute('class')))) focusReceiver=focusReceiver.parentNode;
			receiveside="left";
			mathBlinkSetUp(focusReceiver,receiveside,selectedmrow);
		}
		else
		{//There is nothing above us. We try to search for other <selectable mrows> further up the tree
			selectedmrow=findPreviousSelectable(targetParent);
			focusInFromRight(selectedmrow,false);
		}
	}
	mathResumeBlinking();
}

function focusOutToLeft(target) //Object tries to find someone to rely focus to
{
	if((target)&&(target.parentNode))// If data is supplied correctly
	{
		mathStopBlinking();
		var targetParent=target.parentNode,focusReceiver=null,/*This is an object to receive the focus*/
			receiveside="right"/*on which side the focusReceiver gets the focus, right by default*/,
			selectedmrow=mathBlinkingObj.selectedMrow;
		
		if(target.previousElementSibling!=null)
		{//There are elements on the left. Rely focus to them
			focusInFromRight(target.previousElementSibling,false);
		}
		else
		{//There are no elements to the left. We must look further up the tree.
			if (/selectable/.test(targetParent.getAttribute('class')))
			{//We reached the begining of the selectable <mrow>, jump out of it
				jumpOutToLeft(target,targetParent);
			}
			else //get out of the shell and look again
			{
				focusOutToLeft(targetParent);
			}
		}
		mathResumeBlinking();
	}
}

function focusInFromRight(target,
					self /*Does the object try to move focus around itself, or is it its rightSibling? true or false*/
					)
//Focus moves to the left
{
	if((target)&&(target.parentNode))// If data is supplied correctly
	{
		mathStopBlinking();
		var targetParent=target.parentNode,focusReceiver=null,/*This is an object to receive the focus*/
			receiveside="right"/*on which side the focusReceiver gets the focus, right by default*/,
			selectedmrow=mathBlinkingObj.selectedMrow;
			if (/empty/.test(targetParent.getAttribute('class')))//We are in the empty placeholder move to the previous meaningful element
			{
				if (self)
				{//Empty placeholder tries to move focus around itself. We should get out of it and find other target
					focusOutToLeft(target);
				}
				else
				{//Empty placeholder receives focus from someone on the right
					selectedmrow=targetParent;
					focusReceiver=selectedmrow.lastElementChild;
				}
			}
			else//We are in the nonempty container
			{
				if ((self)&&(self==true)) //The object tries to shuffle focus around itself
				{
					if ((target.childElementCount>0)&&(!(/single/.test(target.getAttribute('class')))))
					{//Composite element. Move inside
						if (/selectable/.test(target.lastElementChild.getAttribute('class')))
						{//Element contains its own input fields
							selectedmrow=target.lastElementChild;
							focusReceiver=selectedmrow.lastElementChild;
						}
						else if (/greyedout/.test(target.lastElementChild.getAttribute('class')))
						{//Element is a greyed out parenthesis item
							focusInFromRight(target.lastElementChild,false);
						}
						else
						{//Element has a complicated structure, let it deal with it
							focusInFromRight(target.lastElementChild,false);
						}
					}
					else //Simple element
					{
						if (/greyedout/.test(target.getAttribute('class')))
						{
							if(target.previousElementSibling!=null)
							{
								focusReceiver=target.previousElementSibling
							}
							else focusOutToLeft(target);
						}
						else
						{
							focusReceiver=target;
							receiveside="left";
						}
					}
				}
				else //Someone from the left tries to rely focus to us
				{
					if ((target.childElementCount>0)&&(!(/single/.test(target.getAttribute('class')))))
					{//Composite element. Move inside
						if (/selectable/.test(target.getAttribute('class')))
						{//Element contains its own input fields
							selectedmrow=target;
							focusReceiver=selectedmrow.lastElementChild;
						}
						else if (/greyedout/.test(target.getAttribute('class')))
						{//Element is a greyed out parenthesis item
							focusInFromRight(target.lastElementChild,false);
						}
						else
						{//We move focus to the last child
							focusInFromRight(target.lastElementChild,false);
						}
					}//Simple element
					else
					{
						if (/greyedout/.test(target.getAttribute('class')))
						{
							if(target.previousElementSibling!=null)
							{
								focusReceiver=target.previousElementSibling
							}
							else focusOutToLeft(target);
						}
						else
						{
							focusReceiver=target;
							receiveside="left";
						}
					}
				}
			}
		if (focusReceiver!=null)
		{
			mathBlinkSetUp(focusReceiver,receiveside,selectedmrow);
		}
		else
		{
			mathResumeBlinking();
		}
	}
}

function removeLast(target,
					suicide,/*Does the object tries to delete pieces of itself? true or false*/
					deleteWhole/*Do we want to delete the whole thing, includeing children? true or false*/
					)
{
	if((target)&&(target.parentNode)&&(isMovable(target)))// If data is supplied correctly
	{
		mathStopBlinking();
		var targetParent=target.parentNode,batonReceiver=null,/*This is an object to receive the focus in case the focuse object delets itself*/
			receiveside="left"/*on which side the batonReceiver gets the focus, left by default*/;
		
		if (target.previousElementSibling)
		{
			batonReceiver=target.previousElementSibling;
			receiveside="right";
		}
		else if (target.nextElementSibling)
		{
			batonReceiver=target.nextElementSibling;
		}
		
		if(/empty/.test(targetParent.getAttribute('class')))
		{//We are hitting backspace in an empty placeholder, do nothing for now. TODO remove the containing template if there is a higher <selectable mrow>
		}
		else
		{//We delete an object
			if(true||deleteWhole)//Delet the whole object in one swipe, even if it has children
			{
				if (suicide) //Object suicide
				{//Object has still focus on itself move it to batonReceiver if it exists
					if (batonReceiver!=null)
					{
						mathBlinkSetUp(batonReceiver,receiveside,findParentMRow(batonReceiver));
					}
					else
					{
						mathBlinkSetUp(null,"",null);
					}
					targetParent.removeChild(target);
				}
				else
				{
					targetParent.removeChild(target);
				}
			}
			else
			{
				switch(target.nodeName)
				{
				case "mi":
				case "mn":
				case "mo":
					//simple nodes
					if (suicide) //Object suicide
					{//Object has still focus on itself move it to batonReceiver if it exists
						if (batonReceiver!=null)
						{
							mathBlinkSetUp(batonReceiver,receiveside,findParentMRow(batonReceiver));
						}
						else
						{
							mathBlinkSetUp(null,"",null);
						}
						targetParent.removeChild(target);
					}
					else
					{
						targetParent.removeChild(target);
					}
					break;
				case "msup":
					//Composite node will start deleting its exponent
					//Don't delete anythig, just move to the exponent
				}
			}
		}
		
		if (targetParent.childElementCount<1)
		{//Container had become empty
			if (/selectable/.test(targetParent.getAttribute('class')))
			{
				insertEmptyElement(targetParent);
				
				mathBlinkSetUp(targetParent.firstElementChild,"left",findParentMRow(targetParent.firstElementChild));
			}
			else //target's parent is a grouping item. Delete it and move focus to upper elements
			{
				var lookUpParent=targetParent,lookUpTarget=targetParent.firstElementChild;
				
				while ((lookUpParent.childElementCount<2)&&(!(/selectable/.test(lookUpParent.getAttribute('class')))))
				{
					if(lookUpTarget!=null) lookUpParent.removeChild(lookUpTarget);
					lookUpTarget=lookUpParent;
					lookUpParent=lookUpParent.parentNode;
				}
				
				batonReceiver=null;
				receiveside="left";
				
				if (lookUpParent.childElementCount>1)
				{
					if (lookUpTarget.previousElementSibling)
					{
						batonReceiver=lookUpTarget.previousElementSibling;
						receiveside="right";
					}
					else if (lookUpTarget.nextElementSibling)
					{
						batonReceiver=lookUpTarget.nextElementSibling;
					}
				}
				
				lookUpParent.removeChild(lookUpTarget);
				
				if(batonReceiver==null)
				{
					insertEmptyElement(lookUpParent);
					
					batonReceiver=lookUpParent.firstElementChild;
				}
				mathBlinkSetUp(batonReceiver,receiveside,findParentMRow(batonReceiver));
			}
		}
		mathResumeBlinking();
	}
}


function processKeyPress(evt)
//This function inserts new elements if character data is produced and a keypress event is generated
{
	if((mathBlinkingOn)&&(mathBlinkingObj.obj!=null)&&(mathBlinkingObj.obj.parentNode!=null)) //If there is a clicked math object
	{
		if ((!mathHighlightingInProgress)&&(!mathDraggingOn)&&(mathBlinkingObj.obj.namespaceURI.indexOf("MathML") > -1)&&(mathBlinkingObj.obj.parentNode.nodeName=="mrow"))//Just paranoid here
		//Let's not type anything outside of the MathML object and let's not type anything inside a MathML template token. Otherwise we might break things.
		{
			var obj=mathBlinkingObj.obj, 
				objParent=obj.parentNode, 
				code=evt.charCode,//which button was pressed
				emptyContainerCheck=false;//If this is true, then we just altered an empty placeholder
			
			if(!((evt.ctrlKey)||(evt.altKey)||(evt.metaKey)))//Let the browsers deal with keystrokes
			{
				var targetSibling=obj, //This is the object that has cursor
					charIn=String.fromCharCode(code);
					
				if (code==32)
				{
					if (!(/empty/.test(objParent.getAttribute('class'))))//Do nothing if there is nothing to get away from
					{//Space pressed inside something nonempty
						var template = {preformatted:false,nodename:"mspace",textdata:charIn,nodeattributes:[["width","mediummathspace"]]};
						if (mathBlinkingObj.side=="left") //new shildren must be added to the left of selected object
						{
							addLeft(targetSibling,template);
						}
						else //new children must be added to the right of selected object
						{
							addRight(targetSibling,template);
						}
					}
				}
				else if (/([A-Z]|[a-z])/.test(charIn)) //Letters will be typeset as math identifiers
				{
					var template = {preformatted:false,nodename:"mi",textdata:charIn};
					if (mathBlinkingObj.side==="left") //new shildren must be added to the left of selected object
					{
						addLeft(targetSibling,template)
					}
					else //new children must be added to the right of selected object
					{
						addRight(targetSibling,template)
					}
				}
				else if (/\d|\./.test(charIn)) //Digits create rows of math numbers, dot is a digit
				{
					var template;
					if (/\./.test(charIn))
					{
						template = {preformatted:false,nodename:"mn",textdata:charIn,nodeattributes:[["class","decimaldot"]]};
					}
					else
					{
						template = {preformatted:false,nodename:"mn",textdata:charIn};
					}
					
					if (mathBlinkingObj.side==="left")
					{
						addLeft(targetSibling,template);
					}
					else
					{
						addRight(targetSibling,template);
					}
				}
				else if (/[\+\-\*\/\^\_\=]/.test(charIn)) // [+] [-] [*] [/] [^] [_] [=] are creating operations
				{
					var template;
					
					switch(charIn)
					{
					case "*":
						charIn=String.fromCharCode(0xB7);
					case "+":
					case "-":
					case "=":
						// Simple <mo> node for these
						template = {preformatted:false,nodename:"mo",textdata:charIn};
						
						if (mathBlinkingObj.side==="left")
						{
							addLeft(targetSibling,template);
						}
						else
						{
							addRight(targetSibling,template);
						}
						
						break;
					case "^":
						// Add an <msup> element. If there is anything to the left of the cursor, it will be raised to the power, otherwise an empty template will be inserted
						var baseExists=false,baseNode=null;;
						
						mathStopBlinking();//Stop blinking
						
						if (!(/empty/.test(objParent.getAttribute('class'))))//Don't do anything if there is nothing to be raised to the power
						{
							if (mathBlinkingObj.side==="right")
							{
								if ((targetSibling.nodeType==1))
								{
									baseExists=true;
								
									baseNode=targetSibling;
								}
							}
							else
							{
								var previousTargetSibling=obj.previousElementSibling;
								if (previousTargetSibling!=null)
								{
									if ((previousTargetSibling.nodeType==1))
									{
										baseExists=true;
								
										baseNode=previousTargetSibling;
										
										targetSibling=previousTargetSibling;
									}
								}
							}
						}
						else
						{
							baseNode = objParent.firstElementChild;
							if (baseNode!=null) baseExists=true;
						}
						if (baseExists) //There is the base that will be raised to the power
						{
							template = {preformatted:false,nodename:"msup",consumetarget:true};

							addLeft(baseNode,template);
						}
						else
						{
							template = {preformatted:false,nodename:"msup",consumetarget:false};
							
							baseNode=targetSibling;
							
							addLeft(baseNode,template);
						}
						mathResumeBlinking();
						break;
					case "/":
						// Add an <mfrac> element. If there is anything to the left of the cursor, it will be put into the numerator, otherwise an empty template will be inserted
						var numeratorExists=false,numeratorNode=null;
						
						mathStopBlinking();//Stop blinking
						
						if (!(/empty/.test(objParent.getAttribute('class'))))//Empty containers receive special treatment
						{
							if (mathBlinkingObj.side==="right")
							{
								if ((targetSibling.nodeType==1))
								{
									numeratorExists=true;
								
									numeratorNode=targetSibling;
								}
							}
							else
							{
								var previousTargetSibling=obj.previousElementSibling;
								if (previousTargetSibling!=null)
								{
									if ((previousTargetSibling.nodeType==1))
									{
										numeratorExists=true;
								
										numeratorNode=previousTargetSibling;
										
										targetSibling=previousTargetSibling;
									}
								}
							}
						}
						else
						{
							numeratorNode = objParent.firstElementChild;
							if (numeratorNode!=null) numeratorExists=true;
						}
						if (numeratorExists) //There is the a node that can serve as a denominator
						{
							template = {preformatted:false,nodename:"mfrac",consumetarget:true};

							addLeft(numeratorNode,template);
						}
						else
						{
							template = {preformatted:false,nodename:"mfrac",consumetarget:false};
							
							numeratorNode=targetSibling;
							
							addLeft(numeratorNode,template);
						}
						mathResumeBlinking();
						break;
					}
				}
				else if (/[\[\(\{\|]/.test(charIn))
				{
					var template;
					if (/empty/.test(objParent.getAttribute('class')))
					{
						emptyContainerCheck=true;
						
						objParent.setAttribute('class',objParent.getAttribute('class').replace(/\s*empty/,""));
					}
					var newNode=document.createElementNS(mathMLnamespace,"mfenced"), //Create a set of objects that will represent an empty placeholder
						newMRow=document.createElementNS(mathMLnamespace,"mrow"),
						newMPhantom=document.createElementNS(mathMLnamespace,"mphantom"),
						newTextNode = document.createTextNode("X");
					
					if(/[\(]/.test(charIn))//left parenthesis pressed
					{
						template = {preformatted:false,nodename:"mfenced",textdata:charIn,nodeattributes:[["open","("],['close',")"]]}
					}
					else if(/[\[]/.test(charIn))//left bracket pressed
					{
						template = {preformatted:false,nodename:"mfenced",textdata:charIn,nodeattributes:[["open","["],['close',"]"]]}
					}
					else if(/[\{]/.test(charIn))//left curly brace pressed
					{
						template = {preformatted:false,nodename:"mfenced",textdata:charIn,nodeattributes:[["open","{"],['close',"}"]]}
					}
					else if(/[\|]/.test(charIn))//vertical bar is pressed
					{
						template = {preformatted:false,nodename:"mfenced",textdata:charIn,nodeattributes:[["open","|"],['close',"|"]]}
					}
					
					if (mathBlinkingObj.side==="left")
					{
						addLeft(targetSibling,template);
					}
					else
					{
						addRight(targetSibling,template);
					}
				}
				
				if (emptyContainerCheck)
				{
					var MPhantom=objParent.firstChild;
					while (MPhantom.nodeName!="mphantom") MPhantom=MPhantom.nextSibling;
					
					objParent.removeChild(MPhantom);
				}
			}
		}
		if (evt. preventDefault) evt.preventDefault();
	}
}

function clickListener(evt)
{
	if (evt.target.namespaceURI.indexOf("MathML") == -1)//User clicked on a non-math object
	{
		if (mathBlinkingOn)
		{
			mathBlinkSetUp(null,"",mathBlinkingObj.selectedMrow);//Stop blinking if a user clicked on a non-math object
		}
	}

}

function mathBlinkSetUp(obj,side,selectedmrow) 
//Here we set up a blinking current typing position cursor and highlight the mrow-element that is going to receive the newly typed elements
{	
	if (mathBlinkingOn)
	{
		try {clearTimeout( mathBlinkingTimer );} catch(e){};
		
		if (mathBlinkingObj.obj!=null)
		{
			var stylestring = mathBlinkingObj.obj.getAttribute('style');
			if (stylestring===null) stylestring="";
			
			mathBlinkingObj.obj.setAttribute('style',stylestring.replace(/border-.*[^;];/,""));
		}
		if (selectedmrow!=null)
		{
			try {mathBlinkingObj.selectedMrow.setAttribute('class',mathBlinkingObj.selectedMrow.getAttribute('class').replace(/\s*selected/,""));}catch(e){};
			mathBlinkingObj.selectedMrow=null;
		}

	}
	if (obj!=null)
	{
		if (obj.nodeName=="mrow")
		{
			if(side=="left")
			{
				mathBlinkSetUp(obj.firstElementChild,side,null)
			}
			else
			{
				mathBlinkSetUp(obj.lastElementChild,side,null)
			}
		}
		else
		{
			mathBlinkingObj.obj=obj;
			mathBlinkingObj.side=side;
		}
		mathBlinkingOn=true;

		if(selectedmrow!=null) 
		{
			mathBlinkingObj.selectedMrow=selectedmrow;
			
			if (selectedmrow.getAttribute('class')!=null)
			{
				selectedmrow.setAttribute('class',selectedmrow.getAttribute('class')+" selected");
			}
			else
			{
				selectedmrow.setAttribute('class',"selected");
			}
		}
		
		mathBlinkingTimer=setTimeout(mathBlinkingCursorOn,20);
	}
	else
	{
		mathBlinkingOn=false;

		mathBlinkingObj.obj=null;
		mathBlinkingObj.side="";
		
		if(mathBlinkingObj.selectedMrow!=null) 
		{
			mathBlinkingObj.selectedMrow.setAttribute('class',mathBlinkingObj.selectedMrow.getAttribute('class').replace(/\s*selected/,""));
			mathBlinkingObj.selectedMrow=null;
		}
	}
}

function mathStopBlinking()
{
	try {clearTimeout( mathBlinkingTimer );} catch(e){};
}

function mathResumeBlinking()
{
	try {clearTimeout( mathBlinkingTimer );} catch(e){};
	if ((mathBlinkingOn)&&(mathBlinkingObj.obj!=null))
	{
		var stylestring = mathBlinkingObj.obj.getAttribute('style');
		
		if (stylestring===null) stylestring="";
	
		stylestring=stylestring.replace(/border-.*[^;];/,"")+"border-"+mathBlinkingObj.side+": 1px solid black ;";
		mathBlinkingObj.obj.setAttribute('style',stylestring);
		mathBlinkingTimer=setTimeout(mathBlinkingCursorOff,500);//Hide the border after half a second
	}
}

function mathBlinkingCursorOn()//Blinking cursor is emulated by blinking either the left or the right border of the math item
{
	try {clearTimeout( mathBlinkingTimer );} catch(e){};
	if (mathBlinkingObj.obj!=null)
	{
		var stylestring = mathBlinkingObj.obj.getAttribute('style');
		
		if (stylestring===null) stylestring="";
	
		stylestring=stylestring.replace(/border-.*[^;];/,"")+"border-"+mathBlinkingObj.side+": 1px solid black ;";
		mathBlinkingObj.obj.setAttribute('style',stylestring);
		mathBlinkingTimer=setTimeout(mathBlinkingCursorOff,500);//Hide the border after half a second
	}
}

function mathBlinkingCursorOff()
{
	try {clearTimeout( mathBlinkingTimer );} catch(e){};
	if (mathBlinkingObj.obj!=null)
	{
		var stylestring = mathBlinkingObj.obj.getAttribute('style');
		
		if(stylestring!=null)
		{
			stylestring=stylestring.replace(/border-.*[^;];/,"");
			mathBlinkingObj.obj.setAttribute('style',stylestring);
		}
		mathBlinkingTimer=setTimeout(mathBlinkingCursorOn,500);//Show the current typing position cursor after half a second
	}
}
